package br.ufpe.cin.mac.middleware.connectors.concrete;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import br.ufpe.cin.mac.middleware.connectors.ConcreteConnector;
import br.ufpe.cin.mac.util.Constants;
import br.ufpe.cin.mac.util.Marshal;
import br.ufpe.cin.mac.util.Message;

/**
 * Class TcpServer
 * 
 * @author Osmany
 * @author Rafael Roque
 * 
 */
public class UDPServer extends ConcreteConnector implements UDPServerMBean {

	// private final int PORT = 1096;

	private DatagramChannel channelServer;

	private Map<String, SocketAddress> connections;

	String type;

	public UDPServer() {
		super(UDPServer.class.getCanonicalName());
		this.connections = Collections
				.synchronizedMap(new HashMap<String, SocketAddress>());

	}

	public void connect() {
		channelServer = null;
		try {
			channelServer = DatagramChannel.open();
			DatagramSocket socket = channelServer.socket();
			socket.bind(new InetSocketAddress(Constants.UDP_PORT));

		} catch (Exception e) {
			e.printStackTrace();
		}
		boolean wait = false;
		while (true) {
			ByteBuffer buffer = null;
			SocketAddress addr = null;

			if (wait) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

			synchronized (channelServer) {
				try {

					channelServer.disconnect();
					buffer = ByteBuffer.allocate(4);
					buffer.clear();
					channelServer.configureBlocking(false);
					addr = channelServer.receive(buffer);
					if (addr == null) {
						wait = true;
						continue;
					} else {
						wait = false;
						channelServer.configureBlocking(true);
					}
					channelServer.connect(addr);
					byte[] readSyze = buffer.array();
					int msgSize = ((0x00000000000000FF & ((Byte) readSyze[0]) << 24)
							| ((0x00000000000000FF & ((Byte) readSyze[1])
									.byteValue()) << 16)
							| ((0x00000000000000FF & ((Byte) readSyze[2])
									.byteValue()) << 8) | (0x00000000000000FF & ((Byte) readSyze[3])
							.byteValue()));
					buffer = null;
					readSyze = null;

					buffer = ByteBuffer.allocate(msgSize);
					buffer.clear();

					channelServer.receive(buffer);
					channelServer.disconnect();

				} catch (IOException e) {
					e.printStackTrace();
					continue;
				}

			}
			Message msg = null;

			try {
				msg = (Message) Marshal.recoverObject(buffer.array());
			} catch (IOException e) {
				e.printStackTrace();
				continue;
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				continue;
			}
			buffer = null;
			connections.put(msg.getSender(), addr);
			invC(msg);
		}
	}

	public void disconnect(Socket socket) throws Exception {
		// TODO Auto-generated method stub
	}

	public void invC(Message msg) {

		this.invS.put(msg);

		sendNotification(msg.hashCode(), "invS");
	}

	public Message invS(int hashMsg) {
		return (Message) this.invS.get(hashMsg);
	}

	public Message terC(int hashMsg) {
		try {
			final Message msg = (Message) this.terC.get(hashMsg);
			new Thread() {
				public void run() {
					send(msg);
				}
			}.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public void terS(Message msg) {
		this.terC.put(msg);

		sendNotification(msg.hashCode(), "terC");
	}

	private void send(Message msg) {
		String client = msg.getReceiver();
		SocketAddress addr = connections.get(client);

		synchronized (channelServer) {

			try {
				channelServer.connect(addr);

				byte[] send = Marshal.serializeObject(msg);
				int size = send.length;
				ByteBuffer buffer = ByteBuffer.allocate(4);
				byte[] msgSize = new byte[4];
				for (int i = 0; i < 4; i++) {
					msgSize[i] = (new Byte(
							(byte) (0x000000FF & (size >> (24 - i * 8)))));
				}

				buffer.put(msgSize);
				buffer.flip();
				int numSend = channelServer.write(buffer);
				if (numSend != 4) {
					System.err.println("ERROR: Send buffer UDP");
					return;
				}
				buffer.clear();

				buffer = ByteBuffer.allocate(size);
				buffer.clear();
				buffer.put(send);
				buffer.flip();
				numSend = channelServer.write(buffer);
				channelServer.disconnect();
				System.out.println("####");
				if (numSend != send.length) {
					System.err.println("ERROR: Send buffer UDP");
					return;
				}
			} catch (IOException e1) {
				e1.printStackTrace();
			}

		}

	}


	protected void msgManager(Message msg) {
	}


	public void setHost(String host) {
		// TODO Auto-generated method stub
		
	}
}
